In order to start using the Spring ORM module for Castor JDO, you will have to have Maven 2 installed:
Download and install Maven 2
As this project uses Maven 2 for build and deployment, all required compile-time and run-time dependencies will automatically be resolved by Maven 2 and deployed into your local Maven 2 repository.
Please add the following Maven dependency to your POM to include the Spring ORM package for Castor JDO with your project:
<dependency> <groupId>org.codehaus.castor</groupId> <artifactId>spring-orm</artifactId> <version>1.3</version> </dependency>
If you create a dependency against a SNAPSHOT release, you will
have to add the following <repository>
element
to your POM as well, so that Maven 2 knows about the
Codehaus Snapshot repository when
trying to resolve and download dependencies.
<repository> <id>codehaus-snapshots</id> <name>Maven Codehaus Snapshots</name> <url>http://snapshots.maven.codehaus.org/maven2/</url> </repository>>
This guide assumes that you are an experienced Castor JDO users that knows how to use Castor's interfaces and classes to interact with a database. If this is not the case, please familiarize yourself with Castor JDO first.
The sample domain objects used in here basically define a Catalogue
,
which is a collection of Product
s. A possible castor JDO mapping could look
as follows:
<class name="org.castor.sample.Catalogue"> <map-to table="catalogue"/> <field name="id" type="long"> <sql name="id" type="integer" /> </field> <field name="products" type="org.castor.sample.Product" collection="arraylist"> <sql many-key="c_id" /> </field> </class> <class name="org.castor.sample.Product"> <map-to table="product"/> <field name="id" type="long"> <sql name="id" type="integer" /> </field> <field name="description" type="string"> <sql name="desc" type="varchar" /> </field> </class>
To e.g. load a given Catalogue
instance as defined by its identity,
and all its associated Product
instances, the following code could be used,
based upon the Castor-specific interfaces JDOManager
and Database
.
JDOManager.loadConfiguration("jdo-conf.xml"); JDOManager jdoManager = JDOmanager.createInstance("sample"); Database database = jdoManager.getDatabase(); database.begin(); Catalogue catalogue = database.load(catalogue.class, new Long(1)); database.commit(); database.close();
For brevity, exception handling has been omitted completely. But is is quite obvious that - when using such code fragments to implement various methods of a DAO - there's a lot of redundant code that needed to be written again and again - and exception handling is adding some additional complexity here as well.
Enters Spring ORM for Castor JDO, a small layer that allows usage of Castor JDO through Spring ORM, with all the known benefits (exception conversion, templates, tx handling).
Let's see how one might implement the loadProduct(int)
of a
ProductDAO
class with the help of Spring ORM using Castor
JDO:
public class ProductDaoImpl implements ProductDao { private JDOManager jdoManager; public void setJDOManager(JDOManager jdoManager) { this.jdoManager = jdoManager; } public Product loadProduct(final int id) { CastorTemplate tempate = new CastorTemplate(this.jdoManager); return (Product) template.execute( new CastorCallback() { public Object doInJdo(Database database) throws PersistenceException { return (Product) database.load(Product.class, new Integer (id)); } }); } }
Still a lot of code to write, but compared to the above section, the DAO gets
passed a fully configured JDOManager
instance through Spring's dependency
injection mechanism. All that's required is configuration of Castor's JDOManager
as a Spring bean definition in an Spring application context as
follows.
<bean id="jdoManager" class="org.castor.spring.orm.LocalCastorFactoryBean"> <property name="databaseName" value="test" /> <property name="configLocation" value="classpath:jdo-conf.xml" /> </bean> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="JDOManager"> <ref bean="jdoManager"/> </property> </bean>
Above code is still quite verbose, as it requires you to write short
(though complex) callback functions. To ease life of the Castor JDO
user even more, a range of template methods have been added to
CastorTemplate
, allowing the implementation of
above ProductDAO
to be shortened considerably.
public class ProductDaoImplUsingTemplate extends CastorTemplate implements ProductDao { private JDOManager jdoManager; public void setJDOManager(JDOManager jdoManager) { this.jdoManager = jdoManager; } public Product loadProduct(final int id) { return (Product) load(Integer.valueOf(id)); } ... }
Changing the bean definition for myProductDAO
to ...
<bean id="myProductDao" class="product.ProductDaoImplUsingTemplate"> <property name="JDOManager"> <ref bean="myJdoManager"/> </property> </bean>
loading an instance of Product
by its identifier
is reduced to ...
ProductDao dao = (ProductDAO) context.getBean ("myProductDAO");
Product product = dao.load(1);
Alternatively to extending CastorTemplate
, one could extend the
CastorDaoSupport
class and implement the
ProductDAO
as
follows.
public class ProductDaoImplUsingDaoSupport extends CastorDaoSupport implements ProductDao { private JDOManager jdoManager; public void setJDOManager(JDOManager jdoManager) { this.jdoManager = jdoManager; } public Product loadProduct(final int id) { return (Product) getCastorTemplate().load(Integer.valueOf(id)); } ... }
Changing the bean definition for myProductDAO
to ...
<bean id="myProductDao" class="product.ProductDaoImplUsingDaoSupport"> <property name="JDOManager"> <ref bean="myJdoManager"/> </property> </bean>
the code to load an instance of Product
still
is as shown above.
We will start with a coverage of Hibernate in a Spring environment, using it to demonstrate the approach that Spring takes towards integrating O/R mappers. This section will cover many issues in detail and show different variations of DAO implementations and transaction demarcations.
Typical business applications are often cluttered with repetitive resource management code. Many projects try to invent their own solutions for this issue, sometimes sacrificing proper handling of failures for programming convenience. Spring advocates strikingly simple solutions for proper resource handling, namely IoC via templating; for example infrastructure classes with callback interfaces, or applying AOP interceptors. The infrastructure cares for proper resource handling, and for appropriate conversion of specific API exceptions to an unchecked infrastructure exception hierarchy. Spring introduces a DAO exception hierarchy, applicable to any data access strategy. For direct JDBC, the JdbcTemplate class mentioned in a previous section cares for connection handling, and for proper conversion of SQLException to the DataAccessException hierarchy, including translation of database-specific SQL error codes to meaningful exception classes. It supports both JTA and JDBC transactions, via respective Spring transaction managers.
This module implements Spring ORM/DAO support for Castor JDO, consisting of a CastorTemplate analogous to JdbcTemplate, a CastorInterceptor, and a Castor transaction manager. The major goal is to allow for clear application layering, with any data access and transaction technology, and for loose coupling of application objects. No more business service dependencies on the data access or transaction strategy, no more hard-coded resource lookups, no more hard-to-replace singletons, no more custom service registries. One simple and consistent approach to wiring up application objects, keeping them as reusable and free from container dependencies as possible. All the individual data access features are usable on their own but integrate nicely with Spring's application context concept, providing XML-based configuration and cross-referencing of plain JavaBean instances that don't need to be Spring-aware. In a typical Spring app, many important objects are JavaBeans: data access templates, data access objects (that use the templates), transaction managers, business services (that use the data access objects and transaction managers), web view resolvers, web controllers (that use the business services), and so on.
To avoid tying application objects to hard-coded resource lookups, Spring allows you to define resources like a JDBC DataSource or a Castor JDOManager as beans in an application context. Application objects that need to access resources just receive references to such pre-defined instances via bean references (the DAO definition in the next section illustrates this). The following excerpt from an XML application context definition shows how to set up a JDBC DataSource and a Castor JDOManager on top of it:
<beans> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:hsql://localhost:9001" /> <property name="username" value="sa" /> <property name="password" value="" /> </bean> <bean id="myJDOManager" class="org.castor.spring.orm.LocalCastorFactoryBean"> <property name="databaseName" value="test" /> <property name="configLocation" value="classpath:jdo-conf.xml" /> </bean> </beans>
Note that switching from a local Jakarta Commons DBCP BasicDataSource to a JNDI-located DataSource (usually managed by an application server) is just a matter of configuration:
<beans> <bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/myds" /> </bean> </beans>
You can also access a JNDI-located SessionFactory, using Spring's JndiObjectFactoryBean to retrieve and expose it. However, that is typically not common outside of an EJB context.
The basic programming model for templating looks as follows, for methods that can be part of any custom data access object or business service. There are no restrictions on the implementation of the surrounding object at all, it just needs to provide a Castor JDOManager. It can get the latter from anywhere, but preferably as bean reference from a Spring application context - via a simple setJDOManager(..) bean property setter. The following snippets show a DAO definition in a Spring container, referencing the above defined JDOManager, and an example for a DAO method implementation.
<beans> <bean id="myProductDao" class="org.exolab.castor.dao.ProductDaoImpl"> <property name="JDOManager"><ref bean="jdoManager"/></property> </bean> </beans>
public class ProductDaoImpl implements ProductDao { private Castor castorTemplate; public void setJDOManager(JDOManager jdoManager) { this.castorTemplate = new CastorTemplate(jdoManager); } public Collection loadProductsByCategory(final String category) throws DataAccessException { return (Collection) this.castorTemplate.execute( new CastorCallback() { public Object doInCastor(Database database) throws PersistenceException { database.begin(); OQLQuery query = database.getOQL("select p from org.exolab.castor.dao.ProductDao p " + " where p.category = ?"); query.bind(category); QueryResults results = query.execute(); database.commit(); return Collections.list(); } ); } }
A callback implementation can effectively be used for any Castor data access. CastorTemplate will ensure that Database instances are properly opened and closed, and automatically participate in transactions. The template instances are thread-safe and reusable, they can thus be kept as instance variables of the surrounding class.
For simple single step actions like a single find, load, saveOrUpdate, or delete call, CastorTemplate offers alternative convenience methods that can replace such one line callback implementations. Furthermore, Spring provides a convenient CastorDaoSupport base class that provides a setJDOManager(..) method for receiving a JDOManager, and getJDOManager() and getCastorTemplate()for use by subclasses.
In combination, this allows for very simple DAO implementations for typical requirements:
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException { return this.getCastorTemplate().find("select p from test.Product product where p.category=?", category); } }
As alternative to using Spring's CastorTemplate to implement DAOs, data access code can also be written in a more traditional fashion, without wrapping the Hibernate access code in a callback, while still complying to Spring's generic DataAccessException hierarchy. Spring's CastorDaoSupport base class offers methods to access the current transactional Database and to convert exceptions in such a scenario; similar methods are also available as static helpers on the JDOManagerUtils class. Note that such code will usually pass "false" into the getDatabased(..) method's "allowCreate" argument, to enforce running within a transaction (which avoids the need to close the returned Database, as it's lifecycle is managed by the transaction).
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException, MyException { Database database = getDatabase(getJDOManager(), false); try { List result = database.find( "select p from test.Product p where " + " product.category=?", category, Castor.STRING); if (result == null) { throw new MyException("invalid search result"); } return result; } catch (PersistenceException ex) { throw convertCastorAccessException(ex); } } }
The major advantage of such direct Castor JDO access code is that it allows any checked application exception to be thrown within the data access code, while CastorTemplate is restricted to unchecked exceptions within the callback. Note that one can often defer the corresponding checks and the throwing of application exceptions to after the callback, which still allows working with CastorTemplate. In general, the CastorTemplate class' convenience methods are simpler and more convenient for many scenarios.
Transactions can be demarcated in a higher level of the application, on top of such lower-level data access services spanning any number of operations. There are no restrictions on the implementation of the surrounding business service here as well, it just needs a Spring PlatformTransactionManager. Again, the latter can come from anywhere, but preferably as bean reference via a setTransactionManager(..) method - just like the productDAO should be set via a setProductDao(..) method.
The following snippets show a transaction manager and a business service definition in a Spring application context, and an example for a business method implementation.
<beans> <bean id="myTxManager" class="org.castor.spring.orm.CastorTransactionManager"> <property name="jdoManager" ref="myJDOManager" /> </bean> <bean id="myProductService" class="product.ProductServiceImpl"> <property name="transactionManager" ref="myTxManager" /> <property name="productDao" ref="myProductDao" /> </bean> </beans>
public class ProductServiceImpl implements ProductService { private TransactionTemplate transactionTemplate; private ProductDao productDao; public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } public void setProductDao(ProductDao productDao) { this.productDao = productDao; } public void increasePriceOfAllProductsInCategory(final String category) { this.transactionTemplate.execute( new TransactionCallbackWithoutResult() { public void doInTransactionWithoutResult(TransactionStatus status) { List productsToChange = productDAO.loadProductsByCategory(category); // do the price increase... } } ); } }
Alternatively, one can use Spring's declarative transaction support, which essentially enables you to replace explicit transaction demarcation API calls in your Java code with an AOP transaction interceptor configured in a Spring container. This allows you to keep business services free of repetitive transaction demarcation code, and allows you to focus on adding business logic which is where the real value of your application lies. Furthermore, transaction semantics like propagation behavior and isolation level can be changed in a configuration file and do not affect the business service implementations.
<beans> <bean id="myTxManager" class="org.castor.spring.orm.CastorTransactionManager"> <property name="jdoManager" ref="myJDOManager" /> </bean> <bean id="myProductService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="product.ProductService" /> <property name="target"> <bean class="product.DefaultProductService"> <property name="productDao" ref="myProductDao" /> </bean> </property> <property name="interceptorNames"> <list> <value>myTxInterceptor</value><!-- the transaction interceptor (configured elsewhere) --> </list> </property> </bean> </beans>
public class ProductServiceImpl implements ProductService { private ProductDao productDao; public void setProductDao(ProductDao productDao) { this.productDao = productDao; } // notice the absence of transaction demarcation code in this method // Spring's declarative transaction infrastructure will be demarcating //transactions on your behalf public void increasePriceOfAllProductsInCategory(final String category) { List productsToChange = this.productDAO.loadProductsByCategory(category); // ... } }
Spring's TransactionInterceptor allows any checked application exception to be thrown with the callback code, while TransactionTemplate is restricted to unchecked exceptions within the callback. TransactionTemplate will trigger a rollback in case of an unchecked application exception, or if the transaction has been marked rollback-only by the application (via TransactionStatus). TransactionInterceptor behaves the same way by default but allows configurable rollback policies per method.
The following higher level approach to declarative transactions doesn't use the ProxyFactoryBean, and as such may be easier to use if you have a large number of service objects that you wish to make transactional.
Note | |
---|---|
You are strongly encouraged to read the section entitled Section 9.5, “Declarative transaction management” if you have not done so already prior to continuing. |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <!-- JDOManager, DataSource, etc. omitted --> <bean id="myTxManager" class="org.castor.spring.orm.CastorTransactionManager"> <property name="jdoManager" ref="myJDOManager" /> </bean> <aop:config> <aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods" /> </aop:config> <tx:advice id="txAdvice" transaction-manager="myTxManager"> <tx:attributes> <tx:method name="increasePrice*" propagation="REQUIRED" /> <tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW" /> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice> <bean id="myProductService" class="product.SimpleProductService"> <property name="productDao" ref="myProductDao" /> </bean> </beans>
Both TransactionTemplate and TransactionInterceptor delegate the actual transaction handling to a PlatformTransactionManager instance, which can be a CastorTransactionManager (for a single Castor JDOManager, using a ThreadLocal Database under the hood) or a JtaTransactionManager (delegating to the JTA subsystem of the container) for Castor applications. You could even use a custom PlatformTransactionManager implementation. So switching from native Castor transaction management to JTA, such as when facing distributed transaction requirements for certain deployments of your application, is just a matter of configuration. Simply replace the Castor transaction manager with Spring's JTA transaction implementation. Both transaction demarcation and data access code will work without changes, as they just use the generic transaction management APIs.
For distributed transactions across multiple Castor JDOManager instances, simply combine JtaTransactionManager as a transaction strategy with multiple LocalCastorFactoryBean definitions. Each of your DAOs then gets one specific JDOManager reference passed into it's respective bean property. If all underlying JDBC data sources are transactional container ones, a business service can demarcate transactions across any number of DAOs and any number of session factories without special regard, as long as it is using JtaTransactionManager as the strategy.
<beans> <bean id="myDataSource1" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName value=" java:comp/env/jdbc/myds1" /> </bean> <bean id="myDataSource2" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/myds2" /> </bean> <bean id="myJDOManager1" class="org.castor.spring.orm.LocalCastorFactoryBean"> <property name="databaseName" value="test1" /> <property name="configLocation" value="classpath:jdo-conf-1.xml" /> </bean> <bean id="myJDOManager2" class="org.castor.spring.orm.LocalCastorFactoryBean"> <property name="databaseName" value="test2" /> <property name="configLocation" value="classpath:jdo-conf-2.xml" /> </bean> <bean id="myTxManager" class="org.springframework.transaction.jta.JtaTransactionManager" /> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="jdoManager" ref="myJDOManager1" /> </bean> <bean id="myInventoryDao" class="product.InventoryDaoImpl"> <property name="jdoManager" ref="myJDOManager2" /> </bean> <!-- this shows the Spring 1.x style of declarative transaction configuration --> <!-- it is totally supported, 100% legal in Spring 2.x, but see also above for the sleeker, Spring 2.0 style --> <bean id="myProductService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="myTxManager" /> <property name="target"> <bean class="product.ProductServiceImpl"> <property name="productDao" ref="myProductDao" /> <property name="inventoryDao" ref="myInventoryDao" /> </bean> </property> <property name="transactionAttributes"> <props> <prop key="increasePrice*">PROPAGATION_REQUIRED</prop> <prop key="someOtherBusinessMethod"> PROPAGATION_REQUIRES_NEW </prop> <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop> </props> </property> </bean> </beans>
Both CastorTransactionManager and JtaTransactionManager allow for proper JVM-level cache handling with Castor - without container-specific transaction manager lookup or JCA connector (as long as not using EJB to initiate transactions).
CastorTransactionManager can export the JDBC Connection used by Castor to plain JDBC access code, for a specific DataSource. This allows for high-level transaction demarcation with mixed Castor/JDBC data access completely without JTA, as long as you are just accessing one database! CastorTransactionManager will automatically expose the Castor transaction as JDBC transaction if the passed-in JDOManager has been set up with a DataSource (through the "dataSource" property of the LocalCastorFactoryBean class).
Alternatively, the DataSource that the transactions are supposed to be exposed for can also be specified explicitly, through the "dataSource" property of the CastorTransactionManager class.
In order to build the Sping ORM module for Castor JDO, you will have the following requirements met on your system:
Download and install Maven 2
Download and install a Subversion client.
As this project uses Maven 2 for build and deployment, all required compile-time dependencies will automatically be resolved by Maven 2 and deployed into your local Maven 2 repository.
This section describes how to build the Spring module from a command line using Maven 2. Whilst there is support for Maven 2 in various IDEs (including e.g. Eclipse, IDEA, etc.), using the Maven command line seems to be the most adequate least common denominator.
This section assumes that you have ckecked out the latest sources from the SVN repsitory for the Spring ORM module for Castor JDO. Instructions for doing so are provided here.
Open a command line (shell) on your system, and issue the following commands:
> mvn jar
Above command will compile the sources and create the distribution JAR
in the target
directory of the project root.
To install the newly created distribution JAR into your local Maven 2 repository, please issue the following command:
> mvn install
To create the complete project documentation - in addition to the distribution assembly, please issue ...
> mvn site